package cn.com.wxd.wechat.common.session;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import cn.com.wxd.wechat.common.util.res.StringManager;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * 基于内存的session manager
 */
public class StandardSessionManager implements WxSessionManager, InternalSessionManager {

	protected final Logger log = LoggerFactory.getLogger(StandardSessionManager.class);

	protected static final StringManager sm = StringManager.getManager(Constants.Package);

	/**
	 * The set of currently active Sessions for this Manager, keyed by session identifier.
	 */
	protected Map<String, InternalSession> sessions = new ConcurrentHashMap<String, InternalSession>();

	public WxSession getSession(String sessionId) {
		return getSession(sessionId, true);
	}

	public WxSession getSession(String sessionId, boolean create) {
		if (sessionId == null) {
			throw new IllegalStateException(sm.getString("sessionManagerImpl.getSession.ise"));
		}

		InternalSession session = findSession(sessionId);
		if ((session != null) && !session.isValid()) {
			session = null;
		}
		if (session != null) {
			session.access();
			return session.getSession();
		}

		// Create a new session if requested and the response is not committed
		if (!create) {
			return (null);
		}

		session = createSession(sessionId);

		if (session == null) {
			return null;
		}

		session.access();
		return session.getSession();
	}

	// -------------------------------------- InternalSessionManager
	/**
	 * The descriptive name of this Manager implementation (for logging).
	 */
	private static final String name = "SessionManagerImpl";

	/**
	 * The maximum number of active Sessions allowed, or -1 for no limit.
	 */
	protected int maxActiveSessions = -1;

	/**
	 * Number of session creations that failed due to maxActiveSessions.
	 */
	protected int rejectedSessions = 0;

	/**
	 * The default maximum inactive interval for Sessions created by this Manager.
	 */
	protected int maxInactiveInterval = 30 * 60;

	// Number of sessions created by this manager
	protected long sessionCounter = 0;

	protected volatile int maxActive = 0;

	private final Object maxActiveUpdateLock = new Object();

	/**
	 * Processing time during session expiration.
	 */
	protected long processingTime = 0;

	/**
	 * Iteration count for background processing.
	 */
	private int count = 0;

	/**
	 * Frequency of the session expiration, and related manager operations. Manager operations will be done once for the specified amount of backgrondProcess calls (ie, the lower the amount, the most often the checks
	 * will occur).
	 */
	protected int processExpiresFrequency = 6;

	/**
	 * background processor delay in seconds
	 */
	protected int backgroundProcessorDelay = 10;

	/**
	 * 后台清理线程是否已经开启
	 */
	private final AtomicBoolean backgroundProcessStarted = new AtomicBoolean(false);

	public void remove(InternalSession session) {
		remove(session, false);
	}

	public void remove(InternalSession session, boolean update) {
		if (session.getIdInternal() != null) {
			sessions.remove(session.getIdInternal());
		}
	}

	public InternalSession findSession(String id) {

		if (id == null)
			return (null);
		return sessions.get(id);

	}

	public InternalSession createSession(String sessionId) {
		if (sessionId == null) {
			throw new IllegalStateException(sm.getString("sessionManagerImpl.createSession.ise"));
		}

		if ((maxActiveSessions >= 0) && (getActiveSessions() >= maxActiveSessions)) {
			rejectedSessions++;
			throw new TooManyActiveSessionsException(sm.getString("sessionManagerImpl.createSession.tmase"), maxActiveSessions);
		}

		// Recycle or create a Session instance
		InternalSession session = createEmptySession();

		// Initialize the properties of the new session and return it
		session.setValid(true);
		session.setCreationTime(System.currentTimeMillis());
		session.setMaxInactiveInterval(this.maxInactiveInterval);
		String id = sessionId;
		session.setId(id);
		sessionCounter++;

		return (session);

	}

	public int getActiveSessions() {
		return sessions.size();
	}

	public InternalSession createEmptySession() {
		return (getNewSession());
	}

	/**
	 * Get new session class to be used in the doLoad() method.
	 */
	protected InternalSession getNewSession() {
		return new StandardSession(this);
	}

	public void add(InternalSession session) {

		// 当第一次有session创建的时候，开启session清理线程
		if (!backgroundProcessStarted.getAndSet(true)) {
			Thread t = new Thread(new Runnable() {

				public void run() {
					while (true) {
						try {
							// 每秒清理一次
							Thread.sleep(backgroundProcessorDelay * 1000l);
							backgroundProcess();
						} catch (InterruptedException e) {
							log.error("SessionManagerImpl.backgroundProcess error", e);
						}
					}
				}
			});
			t.setDaemon(true);
			t.start();
		}

		sessions.put(session.getIdInternal(), session);
		int size = getActiveSessions();
		if (size > maxActive) {
			synchronized (maxActiveUpdateLock) {
				if (size > maxActive) {
					maxActive = size;
				}
			}
		}

	}

	/**
	 * Return the set of active Sessions associated with this Manager. If this Manager has no active Sessions, a zero-length array is returned.
	 */

	public InternalSession[] findSessions() {

		return sessions.values().toArray(new InternalSession[0]);

	}

	public void backgroundProcess() {
		count = (count + 1) % processExpiresFrequency;
		if (count == 0)
			processExpires();
	}

	/**
	 * Invalidate all sessions that have expired.
	 */
	public void processExpires() {

		long timeNow = System.currentTimeMillis();
		InternalSession sessions[] = findSessions();
		int expireHere = 0;

		if (log.isDebugEnabled())
			log.debug("Start expire sessions "+getName()+" at "+timeNow+" sessioncount "+sessions.length);
		for (int i = 0; i < sessions.length; i++) {
			if (sessions[i] != null && !sessions[i].isValid()) {
				expireHere++;
			}
		}
		long timeEnd = System.currentTimeMillis();
		if (log.isDebugEnabled())
			log.debug("Start expire sessions "+getName()+" at "+(timeEnd-timeNow)+" sessions "+expireHere);
		processingTime += (timeEnd - timeNow);

	}

	public void setMaxInactiveInterval(int interval) {

		this.maxInactiveInterval = interval;

	}

	/**
	 * Set the manager checks frequency.
	 * @param processExpiresFrequency the new manager checks frequency
	 */

	public void setProcessExpiresFrequency(int processExpiresFrequency) {

		if (processExpiresFrequency <= 0) {
			return;
		}

		this.processExpiresFrequency = processExpiresFrequency;

	}

	public void setBackgroundProcessorDelay(int backgroundProcessorDelay) {
		this.backgroundProcessorDelay = backgroundProcessorDelay;
	}

	/**
	 * Return the descriptive short name of this Manager implementation.
	 */
	public String getName() {

		return (name);

	}

	/**
	 * Set the maximum number of active Sessions allowed, or -1 for no limit.
	 * @param max The new maximum number of sessions
	 */

	public void setMaxActiveSessions(int max) {

		this.maxActiveSessions = max;

	}

}
